package com.sz.biz.logistics.serialno.utils;

import com.sz.biz.logistics.serialno.constants.BillNumberConstants;
import com.sz.biz.logistics.serialno.constants.BillNumberConstants.Characters;
import com.sz.biz.logistics.serialno.constants.BillNumberConstants.PatternParse;
import com.sz.biz.logistics.serialno.constants.BillNumberConstants.Regexs;
import com.sz.biz.logistics.serialno.constants.BillNumberConstants.Symbols;
import com.sz.common.base.utils.StringUtils;

import java.util.HashMap;
import java.util.Map;

import static com.sz.biz.logistics.serialno.constants.BillNumberConstants.Characters.*;

/**
 * Package: com.sz.biz.logistics.channel.utils<br>
 * Description: 单号序列号模式串解析<br>
 * @author WuHan<br>
 * @date 2017-11-27 10:00<br>
 * @serial
 * @since sys-1.1.3.1
 */
public class SerialNumberRangeResolve {
    
    /**
     * 含分隔符的模式串拆分后应包含上下限两个部分。
     * @see Symbols#SYMBOL_SEPERATOR
     */
    public static final int RANGE = 2;
    
    /**
     * 从单号模式串计算起始单号、结尾单号和单号范围。不解析校验位。
     * @param prefix  前缀，由若干位字母/数字组成，包括明文显示的固定字符和通配符代表的可变字符<br>
     * @param serial  序列号，纯数字，包括明文显示的固定数位和通配符代表的可变数位。<br>还可以使用包含"-"的范围形式书写。
     * @param postfix 后缀，由若干位字母/数字组成，包括明文显示的固定字符和通配符代表的可变字符<br>
     *
     * @return Map&lt;String, String&gt;:<br>1.startChannelNumber - 起始单号<br>2.endChannelNumber - 结尾单号<br>3.range - 单号范围<br>
     */
    public static Map<String, String> resolveRangeNew(String prefix, String serial, String postfix) {
        
        long total = 1;
        StringBuilder max = new StringBuilder();
        StringBuilder min = new StringBuilder();
        if (StringUtils.isNotEmpty(prefix)) {
            long prefixTotal = calcWildCard(prefix);
            total = total * prefixTotal;
            String minStr = replaceWildcardWithCharacter(prefix, Characters.NUMBER_START, Characters.LETTER_START,
                                                         Characters.MIXED_CHARACTER_START);
            String maxStr = replaceWildcardWithCharacter(prefix, Characters.NUMBER_END, Characters.LETTER_END,
                                                         Characters.MIXED_CHARACTER_END);
            min.append(minStr);
            max.append(maxStr);
            
        }
        if (StringUtils.isNotEmpty(serial)) {
            //序列号中只有数字
            String minStr;
            String maxStr;
            long serialTotal;
            if (serial.contains(Symbols.SYMBOL_SEPERATOR)) {
                String[] str = serial.split(Symbols.SYMBOL_SEPERATOR);
                
                serialTotal = serialRange(str[0], str[1]);
                minStr = replaceWildcardWithCharacter(str[0], Characters.NUMBER_START, Characters.LETTER_START,
                                                      Characters.MIXED_CHARACTER_START);
                maxStr = replaceWildcardWithCharacter(str[1], Characters.NUMBER_END, Characters.LETTER_END,
                                                      Characters.MIXED_CHARACTER_END);
            } else {
                serialTotal = calcWildCard(serial);
                minStr = replaceWildcardWithCharacter(serial, Characters.NUMBER_START, Characters.LETTER_START,
                                                      Characters.MIXED_CHARACTER_START);
                maxStr = replaceWildcardWithCharacter(serial, Characters.NUMBER_END, Characters.LETTER_END,
                                                      Characters.MIXED_CHARACTER_END);
            }
            total = total * serialTotal;
            min.append(minStr);
            max.append(maxStr);
        }
        if (StringUtils.isNotEmpty(postfix)) {
            long postfixTotal = calcWildCard(postfix);
            total = total * postfixTotal;
            String minStr = replaceWildcardWithCharacter(postfix, Characters.NUMBER_START, Characters.LETTER_START,
                                                         Characters.MIXED_CHARACTER_START);
            String maxStr = replaceWildcardWithCharacter(postfix, Characters.NUMBER_END, Characters.LETTER_END,
                                                         Characters.MIXED_CHARACTER_END);
            min.append(minStr);
            max.append(maxStr);
        }
        Map<String, String> map = new HashMap<>(1);
        map.put(PatternParse.START_CHANNEL_NUMBER, min.toString());
        map.put(PatternParse.END_CHANNEL_NUMBER, max.toString());
        map.put(PatternParse.CHANNEL_NUMBER_RANGE, String.valueOf(total));
        return map;
    }
    
    private static long calcWildCard(String pattern) {
        
        long numbers = (long) Math.pow(PatternParse.NUMBER_BASE, countWildCard(pattern, Symbols.SYMBOL_DIGIT));
        long character = (long) Math.pow(PatternParse.LETTER_BASE, countWildCard(pattern, Symbols.SYMBOL_LETTER));
        long wildCard = (long) Math.pow(PatternParse.MIXCHAR_BASE, countWildCard(pattern, Symbols.SYMBOL_MIXED));
        return numbers * character * wildCard;
    }
    
    /**
     * 使用具体字符替换通配符
     * @param pattern        待替换字符串
     * @param number         替换用的纯数字
     * @param letter         替换用的纯字母
     * @param mixedCharacter 替换用的混合类型
     *
     * @return 替换完成的字符串
     */
    private static String replaceWildcardWithCharacter(String pattern, String number, String letter,
                                                       String mixedCharacter) {
        
        return pattern.replaceAll(Regexs.DOUBLE_SLASH + Symbols.SYMBOL_DIGIT, number).replaceAll(Symbols.SYMBOL_LETTER,
                                                                                                 letter).replaceAll(
            Regexs.DOUBLE_SLASH + Symbols.SYMBOL_MIXED, mixedCharacter);
    }
    
    /**
     * 根据单号模式串中的通配符计算出该模式串最大覆盖范围。
     * @param sectionNamePattern 模式串，见{@link #(String, String,String)}
     * @param symbol             要统计的通配符(* OR # OR !)
     *
     * @return 模式串所能指代的单号数量
     */
    private static int countWildCard(String sectionNamePattern, String symbol) {
        
        int count = 0;
        while (sectionNamePattern.contains(symbol)) {
            switch (symbol) {
                case Symbols.SYMBOL_DIGIT:
                    sectionNamePattern = sectionNamePattern.replaceFirst(Regexs.DOUBLE_SLASH + symbol,
                                                                         Characters.NUMBER_START);
                    break;
                case Symbols.SYMBOL_LETTER:
                    sectionNamePattern = sectionNamePattern.replaceFirst(symbol, Characters.LETTER_START);
                    break;
                case Symbols.SYMBOL_MIXED:
                    sectionNamePattern = sectionNamePattern.replaceFirst(Regexs.DOUBLE_SLASH + symbol,
                                                                         Characters.MIXED_CHARACTER_START);
                    break;
                default:
                    break;
            }
            count++;
        }
        return count;
    }
    
    private static Long serialRange(String sectionStart, String sectionEnd) {
        
        String tempStart = replaceWildcardWithCharacter(sectionStart, NUMBER_START, LETTER_START,
                                                        MIXED_CHARACTER_START);
        if (tempStart.contains(Symbols.SYMBOL_VERIFYCODE)) {
            tempStart = tempStart.replaceAll(Regexs.DOUBLE_SLASH + Symbols.SYMBOL_VERIFYCODE, Characters.EMPTY_STRING);
        }

        while (tempStart.contains(Symbols.SYMBOL_VERIFYCODE)) {
            int pos = tempStart.indexOf(Symbols.SYMBOL_VERIFYCODE);
            tempStart = tempStart.substring(0, pos) + tempStart.substring(pos + 1);
            sectionEnd = sectionEnd.substring(0, pos) + sectionEnd.substring(pos + 1);
        }
        String tempEnd = replaceWildcardWithCharacter(sectionEnd, NUMBER_END, LETTER_END, MIXED_CHARACTER_END);
        if (tempEnd.contains(Symbols.SYMBOL_VERIFYCODE)) {
            tempEnd = tempEnd.replaceAll(Regexs.DOUBLE_SLASH + Symbols.SYMBOL_VERIFYCODE, Characters.EMPTY_STRING);
        }
        return Long.valueOf(tempEnd) - Long.valueOf(tempStart) + 1;
    }
    
    /**
     * 迭代计算字符串差值
     * @param count     上一次迭代计算的结果
     * @param base      本位置字符的进制
     * @param tempStart 起始号码串
     * @param tempEnd   结尾号码串
     *
     * @return 本次迭代计算的结果
     */
    private static long getCount(long count, int base, char tempEnd, char tempStart) {
        
        return count * base + PatternParse.CHARACTER_INTEGER_MAP.get(tempEnd) - PatternParse.CHARACTER_INTEGER_MAP.get(
            tempStart);
    }
    
    public static long offsetCalculate(String current, String prefix, String serial, String postfix) {
        
        int serialNumberLength = 0;
        int prefixLength = 0;
        long prefixOffSet = 0L;
        long serialOffSet = 0L;
        long postfixOffSet = 0L;
        if (StringUtils.isNotEmpty(prefix)) {
            prefixLength = prefix.length();
            prefixOffSet = 1L;
        }
        if (StringUtils.isNotEmpty(serial)) {
            serialNumberLength = serial.length();
            serialOffSet = 1L;
        }
        if (serial.contains(BillNumberConstants.Symbols.SYMBOL_SEPERATOR)) {
            serialNumberLength = serial.split("-")[0].length();
        }
        if (StringUtils.isNotEmpty(postfix)) {
            postfixOffSet = 1L;
        }
        
        String currentPrefix = current.substring(0, prefixLength);
        String currentSerial = current.substring(prefixLength, serialNumberLength + prefixLength);
        String currentPostfix = current.substring(serialNumberLength + prefixLength);
        
        long serialTotal = 1L;
        long postfixTotal = 1L;
        if (StringUtils.isNotEmpty(prefix)) {
            prefixOffSet = calcOffset(currentPrefix, prefix);
        }
        if (StringUtils.isNotEmpty(serial)) {
            if (serial.contains(Symbols.SYMBOL_SEPERATOR)) {
                String[] str = serial.split(Symbols.SYMBOL_SEPERATOR);
                serialTotal = serialRange(str[0], str[1]);
                serialOffSet = serialRange(str[0],currentSerial);
            } else {
                serialTotal = calcWildCard(serial);
                serialOffSet = calcOffset(currentSerial, serial);
            }
        }
        if (StringUtils.isNotEmpty(postfix)) {
            postfixOffSet = calcOffset(currentPostfix, postfix);
            postfixTotal = calcWildCard(postfix);
        }
        return prefixOffSet * serialTotal * postfixTotal + serialOffSet * postfixTotal + postfixOffSet;
    }
    
    private static long calcOffset(String current, String format) {
        
        long count = 0L;
        for (int i = 0; i < current.length(); i++) {
            if (!format.substring(i, i + 1).equals(Symbols.SYMBOL_VERIFYCODE)) {
                if (!current.substring(i, i + 1).equals(format.substring(i, i + 1))) {
                    switch (format.substring(i, i + 1)) {
                        case Symbols.SYMBOL_DIGIT:
                            count = getCount(count, PatternParse.NUMBER_BASE, current.charAt(i),
                                    PatternParse.CHARACTER_INTEGER_ARRAY[0]);
                            break;
                        case Symbols.SYMBOL_LETTER:
                            count = getCount(count, PatternParse.LETTER_BASE, current.charAt(i),
                                    PatternParse.CHARACTER_INTEGER_ARRAY[10]);
                            break;

                        case Symbols.SYMBOL_MIXED:
                            count = getCount(count, PatternParse.MIXCHAR_BASE, current.charAt(i),
                                    PatternParse.CHARACTER_INTEGER_ARRAY[0]);
                            break;
                        default:
                            count = count * PatternParse.NUMBER_BASE + PatternParse.CHARACTER_INTEGER_MAP.get(
                                    current.charAt(i)) - PatternParse.CHARACTER_INTEGER_MAP.get(format.charAt(i));
                            break;
                    }
                }
            }
        }
        return count;
    }
    
    
    public static boolean isRangeTest(String current, String prefix, String serial, String postfix) {
        
        int serialNumberLength = 0;
        int prefixLength = 0;
        if (StringUtils.isNotEmpty(prefix)) {
            prefixLength = prefix.length();
        }
        if (StringUtils.isNotEmpty(serial)) {
            serialNumberLength = serial.length();
        }
        if (serial.contains(Symbols.SYMBOL_SEPERATOR)) {
            serialNumberLength = serial.split(Symbols.SYMBOL_SEPERATOR)[0].length();
        }
        String currentPrefix = current.substring(0, prefixLength);
        String currentSerial = current.substring(prefixLength, serialNumberLength + prefixLength);
        String currentPostfix = current.substring(serialNumberLength + prefixLength);
        if (StringUtils.isNotEmpty(currentPrefix)) {
            if (!checkCurrentIndexMatchFormat(currentPrefix, prefix)) {
                return false;
            }
            if (!isRangeTest(currentPrefix, prefix)) {
                return false;
            }
        }
        if (StringUtils.isNotEmpty(currentSerial)) {
            if (serial.contains(Symbols.SYMBOL_SEPERATOR)) {
                if (!isRangeTest(currentSerial, serial)) {
                    return false;
                }
            } else {
                if (!checkCurrentIndexMatchFormat(currentSerial, serial)) {
                    return false;
                }
                if (!isRangeTest(currentSerial, serial)) {
                    return false;
                }
            }
        }
        if (StringUtils.isNotEmpty(currentPostfix)) {
            if (!checkCurrentIndexMatchFormat(currentPostfix, postfix)) {
                return false;
            }
            if (!isRangeTest(currentPostfix, postfix)) {
                return false;
            }
        }
        return true;
    }
    
    private static boolean isRangeTest(String currentIndex, String format) {
        
        String minStr;
        String maxStr;
        if (format.contains(Symbols.SYMBOL_SEPERATOR)) {
            String[] str = format.split(Symbols.SYMBOL_SEPERATOR);
            String strMin = str[0];
            String strMax = str[1];
            if (strMin.length() != currentIndex.length()) {
                return false;
            }
            while (strMin.contains(Symbols.SYMBOL_VERIFYCODE)) {
                int pos = str[0].indexOf(Symbols.SYMBOL_VERIFYCODE);
                String currentPosStr = currentIndex.substring(pos, pos + 1);
                if (!Symbols.SYMBOL_VERIFYCODE.equals(currentPosStr) && !StringUtils.isNumber(currentPosStr)) {
                    return false;
                }
                strMin = strMin.replace(Symbols.SYMBOL_VERIFYCODE, Characters.EMPTY_STRING);
                strMax = strMax.replace(Symbols.SYMBOL_VERIFYCODE, Characters.EMPTY_STRING);
                currentIndex = currentIndex.substring(0, pos) + currentIndex.substring(pos + 1);
            }
            minStr = replaceWildcardWithCharacter(strMin, Characters.NUMBER_START, Characters.LETTER_START,
                                                  Characters.MIXED_CHARACTER_START);
            maxStr = replaceWildcardWithCharacter(strMax, Characters.NUMBER_END, Characters.LETTER_END,
                                                  Characters.MIXED_CHARACTER_END);
        } else {
            if (format.length() != currentIndex.length()) {
                return false;
            }
            while (format.contains(Symbols.SYMBOL_VERIFYCODE)) {
                int pos = format.indexOf(Symbols.SYMBOL_VERIFYCODE);
                String currentPosStr = currentIndex.substring(pos, pos + 1);
                if (!Symbols.SYMBOL_VERIFYCODE.equals(currentPosStr) &&!StringUtils.isNumber(currentPosStr)) {
                    return false;
                }
                format = format.replace(Symbols.SYMBOL_VERIFYCODE, Characters.EMPTY_STRING);
                currentIndex = currentIndex.substring(0, pos) + currentIndex.substring(pos + 1);
            }
            minStr = replaceWildcardWithCharacter(format, Characters.NUMBER_START, Characters.LETTER_START,
                                                  Characters.MIXED_CHARACTER_START);
            maxStr = replaceWildcardWithCharacter(format, Characters.NUMBER_END, Characters.LETTER_END,
                                                  Characters.MIXED_CHARACTER_END);
        }
        if (minStr.compareTo(currentIndex) <= 0 && maxStr.compareTo(currentIndex) >= 0) {
            return true;
        } else {
            return false;
        }
    }
    
    static boolean checkCurrentIndexMatchFormat(String currentIndex, String format) {
        
        if (currentIndex.length() != format.length()) {
            return false;
        }
        while (format.contains(Symbols.SYMBOL_VERIFYCODE)) {
            int pos = format.indexOf(Symbols.SYMBOL_VERIFYCODE);
            String currentPosStr = currentIndex.substring(pos, pos + 1);
            if(!Symbols.SYMBOL_VERIFYCODE.equals(currentPosStr) && !StringUtils.isNumber(currentPosStr)){
                return  false;
            }
            format = format.replace(Symbols.SYMBOL_VERIFYCODE, Characters.EMPTY_STRING);
            currentIndex = currentIndex.substring(0, pos) + currentIndex.substring(pos + 1);
        }
        for (int i = 0; i < format.length(); i++) {
            if (format.charAt(i) != currentIndex.charAt(i)) {
                switch (format.substring(i, i + 1)) {
                    case Symbols.SYMBOL_LETTER:
                        if (!currentIndex.substring(i, i + 1).matches(Regexs.PURE_LETTER)) {
                            return false;
                        }
                        break;
                    case Symbols.SYMBOL_MIXED:
                        if (!currentIndex.substring(i, i + 1).matches(Regexs.MIXED_CHARACTER)) {
                            return false;
                        }
                        break;
                    case Symbols.SYMBOL_DIGIT:
                        if (!currentIndex.substring(i, i + 1).matches(Regexs.PURE_DIGIT)) {
                            return false;
                        }
                        break;
                    default:
                        return false;
                }
            }
        }
        return true;
    }
}
